﻿#region Usings

using System;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using Ninject;
using Rail.Net.Common.Extensions;
using Rail.Net.Core.Interfaces.Data;
using Rail.Net.Core.Model;

#endregion

namespace Rail.Net.Data
{
    public abstract class Repository<T> : IRepository<T>
        where T : BaseIdentifiable
    {
        protected DataContext Context
        {
            get { return DbContextFactory.GetThreadScopedDataContext(() => new DataContext()); }
        }

        protected Repository()
        {
            Context.Configuration.AutoDetectChangesEnabled = false;
        }

        protected virtual DbSet<T> DbSet
        {
            get { return Context.Set<T>(); }
        }

        #region IRepository<T> Members

        /// <summary>
        ///   Disposes the context
        /// </summary>
        public void Dispose()
        {
            //if (_shareContext && Context != null)
            //    Context.Dispose();
        }

        /// <summary>
        ///   Gets all entities from the DB set including specified properties
        /// </summary>
        /// <param name = "includeProperties">Include properties expression</param>
        /// <returns></returns>
        public virtual IQueryable<T> All(params Expression<Func<T, object>>[] includeProperties)
        {
            var dbSet = DbSet.AsNoTracking().AsQueryable();
            return includeProperties != null ? includeProperties.Aggregate(dbSet, (current, includeProperty) => current.IncludeDeep(includeProperty)) : dbSet;
        }

        public virtual IQueryable<T> AllDefaultsIncluded()
        {
            return All();
        } 

        /// <summary>
        ///   Gets all paged entities from the DB set including specified properties
        /// </summary>
        /// <param name="size">Page size</param>
        /// <param name="index">Page index (zero based)</param>
        /// <param name = "includeProperties">Include properties expression</param>
        /// <returns></returns>
        public virtual IQueryable<T> All(int index = 0, int size = 50, params Expression<Func<T, object>>[] includeProperties)
        {
            var skipCount = index * size;
            var dbSet = DbSet.OrderBy(e => e.Id).AsNoTracking().AsQueryable();
            dbSet =  includeProperties != null ? includeProperties.Aggregate(dbSet, (current, includeProperty) => current.IncludeDeep(includeProperty)) : dbSet;
            return dbSet.AsNoTracking().Skip(skipCount).Take(size);
        }

        /// <summary>
        ///   Filters entities from the DB set based on the given predicate filter
        /// </summary>
        /// <param name = "predicate">Predicate filter</param>
        /// <returns></returns>
        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate)
        {
            return DbSet.Where(predicate).AsNoTracking();
        }

        /// <summary>
        ///   Filters entities from the DB set based on the given predicate filter, given page size and page index
        /// </summary>
        /// <param name = "predicate">Predicate filter</param>
        /// <param name = "index">Page index</param>
        /// <param name = "size">Page size</param>
        /// <returns></returns>
        public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate, int index = 0, int size = 50)
        {
            var skipCount = index*size;
            var resetSet = predicate != null ? DbSet.Where(predicate) : DbSet;
            resetSet = skipCount == 0 ? resetSet.Take(size) : resetSet.Skip(skipCount).Take(size);
            return resetSet.AsNoTracking();
        }

        /// <summary>
        ///   Finds an entity by key from the DB set
        /// </summary>
        /// <param name = "keys">Keys</param>
        /// <returns></returns>
        public virtual T Find(params object[] keys)
        {
            return DbSet.Find(keys);
        }

        /// <summary>
        ///   Finds an entity (first or default) by given predicate expression
        /// </summary>
        /// <param name = "predicate"></param>
        /// <returns></returns>
        public virtual T Find(Expression<Func<T, bool>> predicate)
        {
            return DbSet.FirstOrDefault(predicate);
        }

        /// <summary>
        ///   Creates new entity instance and adds it to the DB set
        /// </summary>
        /// <param name = "instance">Instance</param>
        /// <returns></returns>
        public virtual T Create(T instance)
        {
            var newEntry = DbSet.Add(instance);
            return newEntry;
        }

        /// <summary>
        ///   Updates given entity instance in the DB set
        /// </summary>
        /// <param name = "instance">Instance</param>
        /// <returns></returns>
        public virtual int Update(T instance)
        {
            var entry = Context.Entry(instance);
            DbSet.Attach(instance);
            entry.State = EntityState.Modified;
            return 0;
        }

        /// <summary>
        ///   Deletes given entity instance from the DB set
        /// </summary>
        /// <param name = "instance">Instance</param>
        /// <returns></returns>
        public virtual int Delete(T instance)
        {
            // remove from the DB
            var entry = Context.Entry(instance);
            if (entry.State == EntityState.Detached)
            {
                DbSet.Attach(instance);
            }
            DbSet.Remove(instance);
            return 0;
        }

        #endregion
    }
}